package com.vivimice.lotroller.notification;

import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class SynchronizedNotificationAgent implements NotificationAgent {

	private Map<NotificationType, Set<NotificationListener>> listenerMap =
			new Hashtable<NotificationType, Set<NotificationListener>>();
	private Map<NotificationType, Set<NotificationListener>> pendingRemoveMap =
			new Hashtable<NotificationType, Set<NotificationListener>>();
	private Map<NotificationType, Set<NotificationListener>> pendingAddMap =
			new Hashtable<NotificationType, Set<NotificationListener>>();
	private Map<NotificationType, Iterator<NotificationListener>> notifyingIteratorMap = 
			new Hashtable<NotificationType, Iterator<NotificationListener>>();
	
	public void addListener(NotificationType type, 
			NotificationListener listener) {
		synchronized (notifyingIteratorMap) {
			if (notifyingIteratorMap.containsKey(type)) {
				// already iterating
				// mark this add as pending
				Set<NotificationListener> pendingAdds;
				synchronized (pendingAddMap) {
					pendingAdds = pendingAddMap.get(type);
					if (pendingAdds == null) {
						pendingAdds = Collections.synchronizedSet(new HashSet<NotificationListener>());
						pendingAddMap.put(type, pendingAdds);
					}
				}
				synchronized (pendingAdds) {
					pendingAdds.add(listener);
				}
			} else {
				Set<NotificationListener> listeners;
				synchronized (listenerMap) {
					if (listenerMap.containsKey(type)) {
						listeners = listenerMap.get(type);
					} else {
						listeners = Collections.synchronizedSet(new HashSet<NotificationListener>());
						listenerMap.put(type, listeners);
					}
				}
				listeners.add(listener);
			}
		}
	}
	
	public void removeListener(NotificationType type, 
			NotificationListener listener) {
		synchronized (notifyingIteratorMap) {
			if (notifyingIteratorMap.containsKey(type)) {
				// already iterating
				// mark this remove as pending
				Set<NotificationListener> pendingRemoves;
				synchronized (pendingRemoveMap) {
					pendingRemoves = pendingRemoveMap.get(type);
					if (pendingRemoves == null) {
						pendingRemoves = Collections.synchronizedSet(new HashSet<NotificationListener>());
						pendingRemoveMap.put(type, pendingRemoves);
					}
				}
				synchronized (pendingRemoves) {
					pendingRemoves.add(listener);
				}
			} else {
				Set<NotificationListener> listeners = listenerMap.get(type);
				if (listeners != null) {
					listeners.remove(listener);
				}
			}
		}
	}
	
	public void sendNotification(NotificationType type, Object data) {
		Set<NotificationListener> listeners = listenerMap.get(type);
		if (listeners == null) {
			return;
		}
		
		Iterator<NotificationListener> itor;
		boolean isOuterMost = false;
		synchronized (notifyingIteratorMap) {
			itor = listeners.iterator();
			if (!notifyingIteratorMap.containsKey(type)) {
				notifyingIteratorMap.put(type, itor);
				isOuterMost = true;
			}
		}
		
		// notify listeners
		while (itor.hasNext()) {
			itor.next().onNotify(type, data);
		}
		
		if (isOuterMost) {
			notifyingIteratorMap.remove(type);
			
			// apply pending removes
			Set<NotificationListener> pendingRemoves = pendingRemoveMap.get(type);
			if (pendingRemoves != null) {
				synchronized (pendingRemoves) {
					Iterator<NotificationListener> pendingRemoveItor = pendingRemoves.iterator();
					while (pendingRemoveItor.hasNext()) {
						removeListener(type, pendingRemoveItor.next());
            pendingRemoveItor.remove();
					}
				}
			}
			
			// apply pending add
			Set<NotificationListener> pendingAdds = pendingAddMap.get(type);
			if (pendingAdds != null) {
				synchronized (pendingAdds) {
					Iterator<NotificationListener> pendingAddItor = pendingAdds.iterator();
					while (pendingAddItor.hasNext()) {
						addListener(type, pendingAddItor.next());
						pendingAddItor.remove();
					}
				}
			}
		}
	}

	@Override
	public void sendNotification(NotificationType type) {
		sendNotification(type, null);
	}
}
